package tplEngine

import (
	"bytes"
	_ "embed"
	"encoding/json"
	"fmt"
	"io"
	fs2 "io/fs"
	"log"
	"net/http"
	"path/filepath"
	"sync"
	"text/template"
)

var _fs fs2.FS
var funcs = template.FuncMap{}
var globalTpls []string
var globalTplsExcluceAjax bool
var globalData = map[string]any{}

type Engine struct {
	W        http.ResponseWriter
	R        *http.Request
	appFuncs template.FuncMap
	appData  map[string]any
}

var handlers = map[string]func(egine *Engine){}
var locker sync.RWMutex

func RegHandle(path string, handlerFunc func(engine *Engine)) {
	locker.Lock()
	defer locker.Unlock()
	handlers[path] = handlerFunc
}

func Handle(path string) func(egine *Engine) {
	handle, _ := handlers[path]
	return handle
}

func Init(fs fs2.FS) {
	_fs = fs
}

// SetGlobalTpl 设置全局公共模板
// @params excluceAjax 非Ajax的情况下是否使用公共模板
func SetGlobalTpl(excluceAjax bool, tplArray ...string) {
	globalTpls = tplArray
	globalTplsExcluceAjax = excluceAjax
}

func New(w http.ResponseWriter, r *http.Request) *Engine {
	return &Engine{
		W:        w,
		R:        r,
		appFuncs: template.FuncMap{},
		appData:  map[string]any{},
	}
}

func SetFs(fs fs2.FS) {
	_fs = fs
}

func AddGlobalFun(name string, fun any) {
	funcs[name] = fun
}

func (e *Engine) AddFun(name string, fun any) {
	e.appFuncs[name] = fun
}

func SetGlobalData(data map[string]any) {
	globalData = data
}

func AddGlobalData(name string, val any) {
	globalData[name] = val
}

func (e *Engine) AddAppData(name string, val any) {
	e.appData[name] = val
}

func (e *Engine) GetAppData(name string, defaultVal any) any {
	if val, ok := e.appData[name]; ok {
		return val
	}
	return defaultVal
}

func (e *Engine) Display(name string, data map[string]any, extTpls ...string) {
	name = fmt.Sprintf("%s.html", name)
	tpls := []string{name}
	isAjax := e.R.Header.Get("X-Requested-With") == "XMLHttpRequest" || e.R.Header.Get("Ajax") != "" || e.R.Header.Get("hx-request") != "" || globalTplsExcluceAjax == false

	if len(globalTpls) > 0 && !isAjax {
		for _, tpl := range globalTpls {
			tpls = append(tpls, fmt.Sprintf("%s.html", tpl))
		}
	}

	if len(extTpls) > 0 {
		for _, tpl := range extTpls {
			tpls = append(tpls, fmt.Sprintf("%s.html", tpl))
		}
	}
	t := template.New(filepath.Base(name))
	t.Funcs(funcs)
	if len(e.appFuncs) > 0 {
		t.Funcs(e.appFuncs)
	}
	t, err := t.ParseFS(_fs, tpls...)
	if err != nil {
		http.Error(e.W, err.Error(), http.StatusInternalServerError)
		log.Println(err)
		return
	}
	if data == nil {
		data = map[string]any{}
	}
	for k, v := range e.appData {
		data[k] = v
	}
	for k, v := range globalData {
		//复盖全局数据
		if _, ok := data[k]; ok {
			continue
		}
		data[k] = v
	}
	if _, ok := data["title"]; !ok {
		data["title"] = ""
	}
	data["isAjax"] = isAjax
	err = t.Execute(e.W, data)
	if err != nil {
		e.W.Write([]byte(err.Error()))
	}
}

func (e *Engine) Component(name string, data map[string]any) string {
	name = fmt.Sprintf("%s.html", name)
	t := template.New(filepath.Base(name))
	t.Funcs(funcs)
	if len(e.appFuncs) > 0 {
		t.Funcs(e.appFuncs)
	}
	t, err := t.ParseFS(_fs, name)
	if err != nil {
		return err.Error()
	}
	str := bytes.NewBuffer([]byte{})
	err = t.Execute(str, data)
	return str.String()
}

func (e *Engine) Success(msg string) {
	_, err := fmt.Fprintf(e.W, `<span class="success">%s</span>`, msg)
	if err != nil {
		log.Println(err)
	}
}

func (e *Engine) Error(msg string) {
	_, err := fmt.Fprintf(e.W, `<span class="error">%s</span>`, msg)
	if err != nil {
		log.Println(err)
	}
}

func (e *Engine) Json(data map[string]any) {
	jsonStr, err := json.Marshal(data)
	if err != nil {
		log.Println(err)
		http.Error(e.W, "网络错误", http.StatusInternalServerError)
		return
	}
	_, err = e.W.Write(jsonStr)
	if err != nil {
		log.Println(err)
	}
}

func (e *Engine) JsonError(msg string) {
	e.Json(map[string]any{"msg": msg, "error": 1})
}

func (e Engine) JsonSuccess(data map[string]any, msg ...string) {
	obj := map[string]any{"data": data, "success": 1}
	if len(msg) > 0 {
		obj["msg"] = msg[0]
	}
	e.Json(obj)
}

// 根据文件扩展名确定 Content-Type
func contentType(filePath string) string {
	ext := filepath.Ext(filePath)
	switch ext {
	case ".html":
		//不让访问html文件，需要经过模板解析
		return ""
	case ".css":
		return "text/css"
	case ".js":
		return "application/javascript"
	case ".jpg", ".jpeg", ".jfif":
		return "image/jpeg"
	case ".png":
		return "image/png"
	// 可以根据需要添加更多的文件类型
	case ".bmp":
		return "image/bmp"
	case ".gif":
		return "image/gif"
	case ".ttf":
		return "application/octet-stream"
	case ".woff":
		return "application/x-font-woff"
	case ".woff2":
		return "font/woff2"
	case ".mp3":
		return "audio/mpeg"
	default:
		return ""
	}
}

// 静态资源访问
func (e *Engine) StaticFile() bool {
	// 获取请求的路径
	filePath := e.R.URL.Path
	//处理静态文件
	mime := contentType(filePath)
	if mime != "" {
		// 尝试打开文件
		file, err := _fs.Open(filePath)
		if err != nil {
			// 文件不存在，返回 404
			http.NotFound(e.W, e.R)
			return true
		}
		defer file.Close()

		// 获取文件信息
		fileInfo, err := file.Stat()
		if err != nil {
			// 获取文件信息失败，返回 500
			http.Error(e.W, err.Error(), http.StatusInternalServerError)
			return true
		}

		// 如果是目录，返回 403
		if fileInfo.IsDir() {
			http.Error(e.W, "Forbidden", http.StatusForbidden)
			return true
		}

		// 设置响应头
		e.W.Header().Set("Content-Type", mime)

		// 服务文件内容
		buf := new(bytes.Buffer)
		_, err = io.Copy(buf, file)
		http.ServeContent(e.W, e.R, filePath, fileInfo.ModTime(), bytes.NewReader(buf.Bytes()))
		return true
	}
	return false
}
